import 'package:built_collection/built_collection.dart';
import 'package:backend.core/core/domain/errors/failures.dart';
import 'package:backend.core/core/domain/utils/either_extension.dart';
import 'package:backend.core/core/domain/utils/uuid_generator.dart';
import 'package:dartz/dartz.dart';
import 'package:backend.core/core/domain/entities/app_store/app_store_transaction.dart';
import 'package:backend.purchases/domain/entities/app_store_api/app_store_api_notification.dart';
import 'package:backend.purchases/domain/entities/app_store_api/app_store_api_purchased_subscription_renewal_info.dart';
import 'package:backend.purchases/domain/entities/app_store_api/app_store_api_transaction.dart';
import 'package:backend.purchases/domain/helpers/app_store/create_app_store_purchased_subscription_info_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/app_store/create_app_store_transaction_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/app_store/update_app_store_purchased_subscription_info_from_api_one.dart';
import 'package:backend.purchases/domain/repositories/app_store_api_purchased_item_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/app_store_purchased_subscription_info_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/app_store_transaction_repository/repository.dart';

part 'failures.dart';

abstract class HandleDidFailToRenewAppStoreApiNotification {
  Future<Either<DidFailToRenewAppStoreApiNotificationHandlingFailure, void>> call({
    required DidFailToRenewAppStoreApiNotification notification,
  });
}

class HandleDidFailToRenewAppStoreApiNotificationImpl
    implements HandleDidFailToRenewAppStoreApiNotification {
  const HandleDidFailToRenewAppStoreApiNotificationImpl({
    required CreateAppStoreTransactionFromApiOne createAppStoreTransactionFromApiOne,
    required CreateAppStorePurchasedSubscriptionInfoFromApiOne
        createPurchasedSubscriptionInfoFromApiOne,
    required UpdateAppStorePurchasedSubscriptionInfoFromApiOne
        updateAppStorePurchasedSubscriptionInfoFromApiOne,
    required AppStoreApiPurchasedItemRepository apiPurchasedItemRepository,
    required AppStoreTransactionRepository transactionRepository,
    required AppStorePurchasedSubscriptionInfoRepository purchasedSubscriptionInfoRepository,
  })  : _createAppStoreTransactionFromApiOne = createAppStoreTransactionFromApiOne,
        _createPurchasedSubscriptionInfoFromApiOne = createPurchasedSubscriptionInfoFromApiOne,
        _updateAppStorePurchasedSubscriptionInfoFromApiOne =
            updateAppStorePurchasedSubscriptionInfoFromApiOne,
        _apiPurchasedItemRepository = apiPurchasedItemRepository,
        _transactionRepository = transactionRepository,
        _purchasedSubscriptionInfoRepository = purchasedSubscriptionInfoRepository;

  final CreateAppStoreTransactionFromApiOne _createAppStoreTransactionFromApiOne;
  final CreateAppStorePurchasedSubscriptionInfoFromApiOne
      _createPurchasedSubscriptionInfoFromApiOne;
  final UpdateAppStorePurchasedSubscriptionInfoFromApiOne
      _updateAppStorePurchasedSubscriptionInfoFromApiOne;

  final AppStoreApiPurchasedItemRepository _apiPurchasedItemRepository;
  final AppStoreTransactionRepository _transactionRepository;
  final AppStorePurchasedSubscriptionInfoRepository _purchasedSubscriptionInfoRepository;

  @override
  Future<Either<DidFailToRenewAppStoreApiNotificationHandlingFailure, void>> call({
    required DidFailToRenewAppStoreApiNotification notification,
  }) async {
    final updatedApiRenewalInfo = notification.renewalInfo;

    final purchasedSubscriptionInfo = await _purchasedSubscriptionInfoRepository
        .findPurchasedSubscriptionInfoByOriginalTransactionId(
      originalTransactionId: updatedApiRenewalInfo.originalTransactionId,
    );

    if (purchasedSubscriptionInfo != null) {
      final updatedPurchasedSubscriptionInfo = _updateAppStorePurchasedSubscriptionInfoFromApiOne(
        purchasedSubscriptionInfo: purchasedSubscriptionInfo,
        updatedApiRenewalInfo: updatedApiRenewalInfo,
      );

      await _purchasedSubscriptionInfoRepository.upsertPurchasedSubscriptionInfo(
        purchasedSubscriptionInfo: updatedPurchasedSubscriptionInfo,
      );

      return right(null);
    }

    final futureResults = await Future.wait([
      _apiPurchasedItemRepository.getTransactionHistory(
        originalTransactionId: updatedApiRenewalInfo.originalTransactionId,
      ),
      _apiPurchasedItemRepository.getRenewalInfos(
        originalTransactionId: updatedApiRenewalInfo.originalTransactionId,
      ),
    ]);

    var i = 0;
    final failureOrTransactionHistory = futureResults[i++]
        as Either<AppStoreApiTransactionHistoryGettingFailure, BuiltList<AppStoreApiTransaction>>;
    final failureOrRenewalInfos = futureResults[i++] as Either<
        AppStoreApiPurchasedSubscriptionRenewalInfoGettingFailure,
        BuiltList<AppStoreApiPurchasedSubscriptionRenewalInfo>>;

    if (failureOrTransactionHistory.isLeft() || failureOrRenewalInfos.isLeft()) {
      return left(const DidFailToRenewAppStoreApiNotificationHandlingFailure());
    }

    final apiTransactionHistory = failureOrTransactionHistory.asRight();

    final apiTransactionIds = apiTransactionHistory.map((transaction) {
      return transaction.id;
    }).toBuiltList();

    final nullableTransactions = await _transactionRepository.findTransactionsByIds(
      ids: apiTransactionIds,
    );
    final transactions = nullableTransactions.whereType<AppStoreTransaction>();

    final String appStoreUserId;

    if (transactions.isNotEmpty) {
      appStoreUserId = transactions.first.appStoreUserId;
    } else {
      appStoreUserId = UuidGenerator().generateUuid();
    }

    final updatedTransactions = apiTransactionHistory.map((apiTransaction) {
      return _createAppStoreTransactionFromApiOne(
        apiTransaction: apiTransaction,
        appStoreUserId: appStoreUserId,
      );
    }).toBuiltList();

    final apiRenewalInfos = failureOrRenewalInfos.asRight();

    final updatedPurchasedSubscriptionInfos = apiRenewalInfos.map((apiRenewalInfo) {
      return _createPurchasedSubscriptionInfoFromApiOne(
        apiRenewalInfo: apiRenewalInfo,
        appStoreUserId: appStoreUserId,
      );
    }).toBuiltList();

    await Future.wait([
      _transactionRepository.replaceUserTransactions(
        appStoreUserId: appStoreUserId,
        transactions: updatedTransactions,
      ),
      _purchasedSubscriptionInfoRepository.replaceUserPurchasedSubscriptionInfos(
        appStoreUserId: appStoreUserId,
        purchasedSubscriptionInfos: updatedPurchasedSubscriptionInfos,
      ),
    ]);

    return right(null);
  }
}
